/*
 * DataLinkLaye.cpp
 *
 *  Created on: Mar 14, 2013
 *      Author: Xi
 */

#include "DataLinkLayer.h"
#include "Utilities.h"
#include "PhysicalLayer.h"
#include "ApplicationLayer.h"
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>
#include"Client.h"
#include<sstream>
#include<math.h>
#include <fstream>

Client c;
int expected_ACK = 1;
pthread_t threadId1; /* recv thread */
pthread_t timerThread[8];

int ACKIndex = 1; /*index for next frame's header*/
vector<string> queue, window; /*if the window is full, put frame in queue*/
vector<string> frameChunk; /*store frame chunks and pack up to packet then pass to application layer*/
bool recvFlag = true; /*recv only called once, then runs recursively*/
int recvfd; /*fd for socket*/
int fdptr; /*pointer to fd*/
int errorRate = 0; /*error rate*/

/**
 * Default constructor, just instantiate object
 */
DataLinkLayer::DataLinkLayer() {

}

/**
 * Default destructor
 */
DataLinkLayer::~DataLinkLayer() {

}

/**
 * Return payloads from a set of frames
 * @param frames
 * @return 
 */
string getPayload(vector<string> frames) {
    string packet;
    int i = 0;
    for (i = 0; i < frames.size(); i++) {
        string payload;
        payload = frames.at(i).substr(7);
        payload = payload.substr(0, payload.size() - 3);
        packet += payload;
    }
    return packet;
}

/**
 * Return payload from a frame
 * @param frame
 * @return 
 */
string getPayload(string frame) {

    string packet = "";
    string payload = "";
    payload = frame.substr(7);
    payload = payload.substr(0, payload.size() - 3);
    packet += payload;

    return packet;
}

/**
 * Return ACK from a frame
 * @param frame
 * @return 
 */
string getACK(string frame) {

    string ack = frame.substr(5, 2);
    return ack;
}

/**
 * Return length in header from a frame
 * @param frame
 * @return 
 */
string getLength(string frame) {
    string length = frame.substr(1, 2);
    return length;
}

/**
 * Return sequence from frame
 * @param frame
 * @return 
 */
string getSeq(string frame) {
    string seq = frame.substr(3, 2);
    return seq;
}

/**
 * Key function for data link layer- it runs recursively to receiving frame from physical layer, then judge whether it's ACKs or frames;
 * For ACKs- if arriving ACK equals to expected ACK, expand expected ACK, else goto next loop wait for timer event resend the frame;
 * For frames- first do checksum, if the frame is correct return the ACK back, then pass payload to application layer, else simply end the code.
 * @param fdptr
 * @return 
 */
void* recvACK(void *fdptr) {

    Utilities u;
    u.echoDebugInfo("Inside DataLinkLayer recvACK(), runs recursively");
    recvfd = *((int*) fdptr);
    int fd = recvfd;

    PhysicalLayer phys;
    bool run = true;

    while (run) {
        string frame = phys.toDLLayer(fd);
        if (frame.length() <= 2 && frame.length() > 0) {

            /*arrive frame is ACK*/
            int ACK = atoi(frame.c_str());
            if (ACK == expected_ACK) {
                /*stop timer*/
//                pthread_cancel(timerThread[ACK - 1]);

                /*slide window*/
                cout << "current window size : " << window.size() << endl;
                window.erase(window.begin());

                stringstream sss;
                sss << ACK;
                string temp = sss.str();
                string deInfo = "Inside DataLinkLayer: received an ACK : " + temp;
                u.echoDebugInfo(deInfo);
                cout << deInfo << endl;

                /*check queue, if not empty, push element in*/
                if (queue.size() > 0) {
                    cout << "in queue block?!" << endl;
                    int size = 8 - window.size();
                    int dif = size - queue.size();
		//    cout << "SIZE: " << size << endl;
		 //   cout << "WINDOWSIZE: " << window.size() << endl;
		  //  cout << "QUEUESIZE: " << queue.size() << endl;
		   // cout << "DIFF: " << dif << endl;
		   // cout << "EXCEPTED FCK: " << expected_ACK << endl;
                    if (dif >= 0)
                        for (int i = 0; i < queue.size(); i++)
                            window.push_back(queue.at(i));
                    else
                        for (int i = 0; i < size; i++) {
                            window.push_back(queue.at(i));
                            queue.erase(queue.begin() + i);
                        }
                }

                /*update expected ACK*/
                if (expected_ACK == 8) {
                    expected_ACK = 1;
                } else
                    expected_ACK++;
                continue;
            }
            continue;
        } else if (frame.length() >= 6) {

            /*received a frame*/
            //checksum
	int l = frame.length();		
	int i,j;
	unsigned CRC,gx;
	CRC = 0;
	char *crcCheck;//checksum in hex
	char *crcRecv; //checksum in frame 
	crcCheck = (char*)malloc(2);
	crcRecv = (char*)malloc(2);
	gx = 0x107;

	crcRecv[0] = frame[l-3];
	crcRecv[1] = frame[l-2];

	for(i=0;i<l-3;i++)	//frame, from the first byte to last before checksum byte 	
	{
		CRC ^= frame[i];
		for(j=0; j<8; j++)
		{
			if(CRC & 0x80)
				CRC = (CRC << 1) ^ gx;
			else
				CRC = (CRC << 1);
		}
		CRC &= 0xff;
	}
		
	sprintf(crcCheck, "%02x", CRC);	
	cout << "frame: " << frame.c_str() << endl;
//	cout << " checksum: "<< crcCheck << endl;	
//	cout << " Recv checksum: "<< crcRecv << endl;	

	if (strcmp(crcCheck, crcRecv) != 0)
	{
        	cout << "checkcheckcheck" << endl;
	//        u.echoDebugInfo("Inside DataLinkLayer: receive a damaged frame");
	//	continue;
	}
	free(crcCheck);
	free(crcRecv);
	            /*return ACK*/
            string ack = getACK(frame);

            /*check ACK correctness*/
            int ACKVal = atoi(ack.c_str());
            if (ACKVal > 8) {
//                u.echoDebugInfo("Inside DataLinkLayer: receive a damaged frame");
		continue;
            } else {
                /*return ACK*/
//	    	cout << "ACKVAL: " << ACKVal << endl;
                phys.fromDLLayer(ack, fd);
                /*get frame length*/
                string lengthStr = getLength(frame);
                int length = atoi(lengthStr.c_str());
//	    	cout << "from datalink layer: " << length << endl;
                u.echoDebugInfo("Inside DataLinkLayer: received a frame");
                if (length == 3) {
                    /*push frame in vector, each 3 frames could(if no errors) forms a packet*/
                    //judge whether it's resend due to frame error
                    if (frameChunk.size() != 0 && frameChunk.size() < 3) {

                        frameChunk.push_back(frame);
//			cout << "FRAMESIZE: " << frameChunk.size() << endl;		
//	    		cout << "FRAME < 3: " << frame << endl;
                        /*received all frames*/

                        /* frame should arrive in order, such that:
                         * 
                         * ACK[n+1] - ACK[n] =  1  | 1  ||
                         * ACK[n+1] - ACK[n] = -7  | 1  ||
                         * ACK[n+1] - ACK[n] =  1  | -7 
                         * &&
                         * SEQ[n+1] - SEQ[n] = 1
                         *
                         */
                        if (frameChunk.size() == 3) {
                            string ACK1Str = getACK(frameChunk.at(0));
                            int ACK1 = atoi(ACK1Str.c_str());

                            string ACK2Str = getACK(frameChunk.at(1));
                            int ACK2 = atoi(ACK2Str.c_str());

                            string ACK3Str = getACK(frameChunk.at(2));
                            int ACK3 = atoi(ACK3Str.c_str());

                            string Seq1Str = getSeq(frameChunk.at(0));
                            int Seq1 = atoi(Seq1Str.c_str());

                            string Seq2Str = getSeq(frameChunk.at(1));
                            int Seq2 = atoi(Seq2Str.c_str());

                            string Seq3Str = getSeq(frameChunk.at(2));
                            int Seq3 = atoi(Seq3Str.c_str());

                            /*calculate value*/
                            int a1 = ACK2 - ACK1;
                            int a2 = ACK3 - ACK2;

                            int b1 = Seq2 - Seq1;
                            int b2 = Seq3 - Seq2;
				
                            if (((a1 == 1 && a2 == 1) || (a1 == -7 && a2 == 1) || (a1 == 1 && a2 == -7)) && (b1 == 1 && b2 == 1)) {
                                string packet = getPayload(frameChunk);
                                ApplicationLayer appl;
                                frameChunk.clear();

	    			cout << "PACKET: " << packet << endl;
                                appl.toCmd(packet, "", fd);
                                continue;
                            } else {
                                //errors in some frames, clear all current frames
                                frameChunk.clear();
                            }
                        }
                    } else if (frameChunk.size() == 0) {
                        /*first frame*/
                        frameChunk.push_back(frame);
                        continue;
                    }
                } else if (length == 2) {

                    /*push frame in vector, each 3 frames could(if no errors) forms a packet*/
                    //judge whether it's resend due to frame error
                    if (frameChunk.size() != 0 && frameChunk.size() < 2) {

                        frameChunk.push_back(frame);

                        /*received all frames*/

                        /* frame should arrive in order, such that:
                         * 
                         * ACK[n+1] - ACK[n] =  1  | -7 
                         * &&
                         * SEQ[n+1] - SEQ[n] = 1
                         *
                         */
                        if (frameChunk.size() == 2) {

                            string ACK1Str = getACK(frameChunk.at(0));
                            int ACK1 = atoi(ACK1Str.c_str());

                            string ACK2Str = getACK(frameChunk.at(1));
                            int ACK2 = atoi(ACK2Str.c_str());

                            string Seq1Str = getSeq(frameChunk.at(0));
                            int Seq1 = atoi(Seq1Str.c_str());

                            string Seq2Str = getSeq(frameChunk.at(1));
                            int Seq2 = atoi(Seq2Str.c_str());

                            /*calculate value*/
                            int a1 = ACK2 - ACK1;

                            int b1 = Seq2 - Seq1;

                            if ((a1 == 1 || a1 == -7) && (b1 == 1)) {
                                string packet = getPayload(frameChunk);
                                ApplicationLayer appl;
                                frameChunk.clear();
                                appl.toCmd(packet, "", fd);
                                continue;
                            } else {
                                //errors in some frames, clear all current frames
                                frameChunk.clear();
                            }
                        }
                    } else if (frameChunk.size() == 0) {
                        /*first frame*/
                        frameChunk.push_back(frame);
                        continue;
                    }
                } else if (length == 1) {
                    string packet = getPayload(frame);
                    ApplicationLayer appl;
                    appl.toCmd(packet, " ", fd);
                    continue;
                }
            }
        }
        continue;
    }
    pthread_exit(NULL);

    cout << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl;
}

/**
 * Timer for frame resend event, if received expecting ACK, cancel the thread
 * @param ackPtr
 * @return 
 */
void* startTimer(void* ackPtr) {

    int ack = *((int*) ackPtr);
    usleep(300000); //300ms

    /*re-send frame*/
    PhysicalLayer phys;
    int i = ack - expected_ACK;
    phys.fromDLLayer(window.at(i), fdptr);

}

/**
 * return random number in range min - max
 * @param min
 * @param max
 * @return 
 */
int randomGenerator(int min, int max) {
    int random, max_value = 100, min_value = 0;
    random = rand() % ((max_value + min_value) + min_value);

    return random;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageACK(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the ACK");
    /*replace ACK to 99*/
    string f = s.replace(5, 2, "99");

    return f;
}

/**
 * Damage ACKs, replace ACK to 99
 * @param s
 * @return 
 */
string damageFrame(string s) {
    Utilities u;
    u.echoDebugInfo("Will damage the frame");
    /*replace checksum to 99*/
    string f = s.replace(s.length() - 4, 2, "99");

    return f;
}

/**
 * Set error rate, damage either ACK or checksum if random number less than error rate
 * @param v
 */
void DataLinkLayer::setErrorRate(int v) {
    errorRate = v;
}

/**
 * Receive packet from Application layer, first break into several frames, then add header for each frame, finally do stuffing and pass them to physical layer.
 * Here we use go-back-8.
 * @param packet
 * @param fd
 * @return 
 */
string DataLinkLayer::fromAppLayer(string packet, int fd) {

    /*debug info*/
    utl.echoDebugInfo("Inside DataLinkLayer::fromAppLayer()");
    // header
    vector<string> payloads, frameChunk;
    frameChunk = utl.splitString(packet, ' ', 90);
    if (frameChunk.empty())
        return "a";
    else {
        int i = 0;
        for (i = 0; i < frameChunk.size(); i++) {
            string header;
            stringstream s;
            int size = frameChunk.size();
            s << size;
            string len = s.str();

            header = "~0" + len + "0";
            stringstream ss;
            int v = i + 1;
            ss << v;
            string seq = ss.str();
            header += seq; //5 bytes for, stuffing byte, length and sequence

            // append ACK
            header += "0";
            stringstream ss1;
            ss1 << ACKIndex;
            string ACK = ss1.str();
            header += ACK; //ACK

            /*expand index*/
            if (ACKIndex == 8)
                ACKIndex = 1;
            else
                ACKIndex++;

            string temp = frameChunk.at(i);
            string frame = header;
            frame += temp; // head and payload

            /*footer*/
 
	int l;
	unsigned int CRC,gx;
	CRC = 0;		
	char *crc;//hex crc
	crc = (char *)malloc(2);
	gx = 0x107;		//polynomial G(X)
	l = frame.length();	
//	cout << "frame: " << frame << "length: " << frame.length() << endl;
	int i,j;
	for(i=0;i<l;i++)
	{
	CRC ^= frame[i];
	for(j=0; j<8; j++)
		{
		if(CRC & 0x80)
		{
			CRC = (CRC << 1) ^ gx;
		}
		else
		{
			CRC <<= 1;
		}
		}
	}

//	if (randomGenerator(0, 10) < errorRate)		
//	{
//		CRC ^= 1;	//simulate noise	
//	}

	sprintf(crc, "%02x", CRC);	

	//cout << "checksum:" << crc << endl;

            frame += crc;
            frame += "~"; // stuffing
	    cout << frame.c_str() << endl;
            /*update vector*/
            payloads.push_back(frame);
        }


        /*send out frames*/
        for (int i = 0; i < payloads.size(); i++) {

            string frame;
            frame = payloads.at(i);

            if (window.size() <= 8) {

                /*store frame in window*/
                window.push_back(frame);
                /*damage frame*/
                string dmFrame;
                int random = randomGenerator(0, 100);
                if (random < errorRate) {
                    if (random < (int) (errorRate / 2))
                        dmFrame = damageACK(frame);
                    else
                        dmFrame = damageFrame(frame);
                    phy.fromDLLayer(dmFrame, fd); //send  out  frame
                } else
                    phy.fromDLLayer(frame, fd);

                /*start timer*/
/*                string ackStr = getACK(frame);
                int currentACK = atoi(ackStr.c_str());
                pthread_create(&timerThread[currentACK - 1], 0, &startTimer, (void*) &currentACK);
                pthread_detach(timerThread[currentACK - 1]);
*/
                /*receive in parallel*/
                fdptr = fd;
    		int err;
                if (recvFlag == true) {
                err = pthread_create(&threadId1, NULL, &recvACK, (void*) &fdptr);
                if (err != 0)
		{
			cout << "ptread_create error! " << endl;
		}
		cout << "from applayer ptread created! " << endl;
		    pthread_detach(threadId1);
                    recvFlag = false;
                }

            } else {

                cout << "window is full!!!" << endl;
                queue.push_back(frame);
            }
        }
        return "a";
    }
}

/**
 * Receive function in data link layer, since we want run recv() recursively and separately from main thread, this function only called once by server when new client connected
 * @param fd
 * @return 
 */
string DataLinkLayer::toAppLayer(int fd) {
    utl.echoDebugInfo("Inside DataLinkLayer::toAppLayer()");
    fdptr = fd;
	int err;
		err = pthread_create(&threadId1, NULL, &recvACK, (void*) &fdptr);
                if (err != 0)
		{
			cout << "ptread_create error! " << endl;
		}
		cout << "to applayer ptread created! " << endl;
//    pthread_create(&threadId1, 0, &recvACK, (void*) &fdptr);
    pthread_detach(threadId1);
    recvFlag = false;

    return "b";
}

void DataLinkLayer::sendPic(string param) {

    cout << "Inside senPIc!!!!!!!!!!" << endl;

    ifstream infile("lakers.txt");
    // get size of file
    infile.seekg(0, infile.end);
    int size = infile.tellg();
    infile.seekg(0);

    cout << "________" << size << endl;

    char* buffer;
    buffer = (char*)malloc(size);
    infile.read(buffer, size);
//    cout << buffer << endl;
    infile.close();

    ApplicationLayer app;
    app.fromPic(buffer, recvfd, size);
    free(buffer);
}
